package cn.xiuxius.embedded.keil;

import cn.xiuxius.embedded.dependencyscan.FileType;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import noNamespace.*;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlString;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Getter
@Setter
public class KeilProjectXML {
    private Path outputDirectory;
    private Path listingPath;
    private String outputName;
    private Path binPath;
    private String c51IncludePath;
    private MemoryModel memoryModel;
    private RTOS rtos;
    private boolean createHexFile;
    private List<KeilFile> files;

    @Setter
    @Getter
    @Accessors(chain = true)
    @ToString
    public static class KeilFile {
        private String group;
        private String name;
        private FileType type;
        private String path;
    }

    public static KeilProjectXML loadUvproj(String uvprojPath) throws IOException, XmlException {
        Path path = Paths.get(uvprojPath);
        if (!Files.exists(path)) {
            throw new IOException("File not found: " + uvprojPath);
        }

        // Load XML file
        XmlOptions options = new XmlOptions();
        options.setLoadLineNumbers();
        options.setLoadMessageDigest();
        options.setLoadStripWhitespace();

        ProjectDocument projectDoc = ProjectDocument.Factory.parse(path.toFile(), options);
        ProjectDocument.Project project = projectDoc.getProject();

        // Check for multiple targets
        ProjectDocument.Project.Targets targets = project.getTargets();
        if (targets.sizeOfTargetArray() > 1) {
            throw new UnsupportedOperationException("Only one target is supported");
        }

        ProjectDocument.Project.Targets.Target target = targets.getTargetArray(0);

        // Create KeilProject instance
        KeilProjectXML keilProjectXML = new KeilProjectXML();

        // Set basic properties
        ProjectDocument.Project.Targets.Target.TargetOption targetOption = target.getTargetOption();
        if (targetOption != null) {
            TargetCommonOptionDocument.TargetCommonOption commonOption = targetOption.getTargetCommonOption();
            if (commonOption != null) {
                keilProjectXML.setOutputDirectory(Paths.get(commonOption.getOutputDirectory()));
                keilProjectXML.setListingPath(Paths.get(commonOption.getListingPath()));
                keilProjectXML.setOutputName(commonOption.getOutputName());
                keilProjectXML.setBinPath(Paths.get(commonOption.getBinPath()));
                keilProjectXML.setCreateHexFile(commonOption.getCreateHexFile() == 1);
            }
            Optional.of(targetOption)
                    .map(ProjectDocument.Project.Targets.Target.TargetOption::getTarget51)
                    .map(ProjectDocument.Project.Targets.Target.TargetOption.Target51::getC51)
                    .map(C51Document.C51::getVariousControls)
                    .map(VariousControlsType::getIncludePath)
                    .ifPresent(
                            includePath -> keilProjectXML.setC51IncludePath(includePath.xmlText())
                    );
            Optional.ofNullable(targetOption.getTarget51())
                    .map(ProjectDocument.Project.Targets.Target.TargetOption.Target51::getTarget51Misc)
                    .ifPresent(target51Misc -> {
                        if (!target51Misc.isNil()) {
                            keilProjectXML.setMemoryModel(MemoryModel.fromNum(target51Misc.getMemoryModel()));
                        } else {
                            keilProjectXML.setMemoryModel(MemoryModel.SMALL);
                        }
                        if (!target51Misc.isNil()) {
                            keilProjectXML.setRtos(RTOS.fromNum(target51Misc.getRTOS()));
                        } else {
                            keilProjectXML.setRtos(RTOS.NONE);
                        }
                    });
        }

        // Process groups and files
        ProjectDocument.Project.Targets.Target.Groups groups = target.getGroups();
        if (groups != null) {
            List<KeilFile> fileList = new ArrayList<>();

            for (ProjectDocument.Project.Targets.Target.Groups.Group group : groups.getGroupArray()) {
                String groupName = group.getGroupName();

                ProjectDocument.Project.Targets.Target.Groups.Group.Files groupFiles = group.getFiles();
                if (groupFiles != null) {
                    for (ProjectDocument.Project.Targets.Target.Groups.Group.Files.File file : groupFiles.getFileArray()) {
                        KeilFile keilFile = new KeilFile();
                        keilFile.setGroup(groupName);
                        keilFile.setName(file.getFileName());
                        keilFile.setType(FileType.fromFileName(file.getFileName()));
                        keilFile.setPath(file.getFilePath());

                        fileList.add(keilFile);
                    }
                }
            }

            keilProjectXML.setFiles(fileList);
        }

        return keilProjectXML;
    }

    public static boolean updateUvproj(KeilProjectXML keilProjectXML, String uvprojPath) throws IOException, XmlException {
        Path path = Paths.get(uvprojPath);
        if (!Files.exists(path)) {
            throw new IOException("File not found: " + uvprojPath);
        }

        // Load existing project
        XmlOptions options = new XmlOptions();
        options.setLoadLineNumbers();
        options.setLoadMessageDigest();
        options.setLoadStripWhitespace();

        ProjectDocument projectDoc = ProjectDocument.Factory.parse(path.toFile(), options);
        ProjectDocument.Project project = projectDoc.getProject();

        // Check for multiple targets
        ProjectDocument.Project.Targets targets = project.getTargets();
        if (targets.sizeOfTargetArray() > 1) {
            log.error("Only one target is supported");
            return false;
        }

        ProjectDocument.Project.Targets.Target target = targets.getTargetArray(0);

        // Update target options
        ProjectDocument.Project.Targets.Target.TargetOption targetOption = target.getTargetOption();
        if (targetOption != null) {
            TargetCommonOptionDocument.TargetCommonOption commonOption = targetOption.getTargetCommonOption();
            if (commonOption != null) {
                // Update common options
                commonOption.setOutputDirectory(toEscapedString(keilProjectXML.getOutputDirectory()));
                commonOption.setListingPath(toEscapedString(keilProjectXML.getListingPath()));
                commonOption.setOutputName(keilProjectXML.getOutputName());
                commonOption.setBinPath(toEscapedString(keilProjectXML.getBinPath()));
                commonOption.setCreateHexFile(keilProjectXML.isCreateHexFile() ? 1 : 0);
            }
            Optional.of(targetOption)
                    .map(ProjectDocument.Project.Targets.Target.TargetOption::getTarget51)
                    .map(ProjectDocument.Project.Targets.Target.TargetOption.Target51::getC51)
                    .map(C51Document.C51::getVariousControls)
                    .ifPresent(variousControls -> {
                        String includePathStr = handleKeilIncludePath(keilProjectXML.getC51IncludePath());
                        XmlString xmlIncludePath = XmlString.Factory.newInstance();
                        xmlIncludePath.setStringValue(includePathStr);
                        variousControls.setIncludePath(xmlIncludePath);
                    });

            Optional.of(targetOption)
                    .map(ProjectDocument.Project.Targets.Target.TargetOption::getTarget51)
                    .ifPresent(target51 -> {
                        Target51MiscDocument.Target51Misc target51Misc = target51.getTarget51Misc();
                        target51Misc.setMemoryModel(keilProjectXML.getMemoryModel().getNum());
                        target51Misc.setRTOS(keilProjectXML.getRtos().getNum());
                    });

        }

        // Update file groups
        if (keilProjectXML.getFiles() != null && !keilProjectXML.getFiles().isEmpty()) {
            ProjectDocument.Project.Targets.Target.Groups groups = target.getGroups();
            if (groups == null) {
                groups = target.addNewGroups();
            }

            // Clear existing groups (or implement more sophisticated merging)
            groups.setGroupArray(null);

            // Group files by their group names
            // Group files by their group names
            Map<String, List<KeilFile>> groupedFiles = keilProjectXML.getFiles().stream()
                    .collect(Collectors.groupingBy(KeilFile::getGroup));

            // Create new groups with files
            for (Map.Entry<String, List<KeilFile>> entry : groupedFiles.entrySet()) {
                ProjectDocument.Project.Targets.Target.Groups.Group group = groups.addNewGroup();
                group.setGroupName(entry.getKey());

                ProjectDocument.Project.Targets.Target.Groups.Group.Files files = group.addNewFiles();

                for (KeilFile keilFile : entry.getValue()) {
                    ProjectDocument.Project.Targets.Target.Groups.Group.Files.File file = files.addNewFile();
                    file.setFileName(keilFile.getName());
                    file.setFileType((byte) keilFile.getType().getNum());
                    file.setFilePath(keilFile.getPath());
                }
            }
        }
        // Save the updated project
        XmlOptions saveOptions = new XmlOptions();
        saveOptions.setSavePrettyPrint();
        saveOptions.setSaveOuter();
        saveOptions.setUseDefaultNamespace();
        projectDoc.save(path.toFile(), saveOptions);
        return true;
    }


    private static String toEscapedString(Path path) {
        String str = path.toString();
        return toEscapedString(str);
    }

    private static String toEscapedString(String path) {
        String sep = File.separator;
        if (path.endsWith("/") || path.endsWith("\\")) {
            return path;
        }
        return path + sep;
    }

    private static String handleKeilIncludePath(String path) {
        // ./xxx;./xxx -> .\xxx;.\xxx
        return path.replace("/", "\\");
    }


}